#include <retro.h>
#include "retro.creator.h"

using namespace Retro::Creator::Editor;
using namespace SPLib::Geom;

EditSurface::EditSurface()
{
  scroll.pos.x = scroll.pos.y = 0;
  editor = NULL;
}

VOID    EditSurface::onSize(Event::SizeEvent& iEvent)
{
  updateScroll();
}

VOID    EditSurface::onHScroll(Event::ScrollEvent& iEvent)
{
  setScrollX(calcScrollPos(iEvent.request, SB_HORZ, scroll.pos.x));
}

VOID    EditSurface::onVScroll(Event::ScrollEvent& iEvent)
{
  setScrollY(calcScrollPos(iEvent.request, SB_VERT, scroll.pos.y));
}

INT     EditSurface::calcScrollPos(
  INT sbRequest,
  INT nBar,
  INT current
  )
{
  if (!editor) return 0;
  if (!editor->isVisible()) return 0;

  SCROLLINFO si;
  si.cbSize = sizeof(si);
  si.fMask = SIF_TRACKPOS;

  GetScrollInfo(*this, nBar, &si);

  switch (sbRequest)
  {
  case SB_PAGEUP:
    return (INT)(current -
      editor->getLineSize() * editor->getZoom() / 100 * 5);
  case SB_PAGEDOWN:
    return (INT)(current +
      editor->getLineSize() * editor->getZoom() / 100 * 5);
  case SB_LINEUP:
    return (INT)(current -
      editor->getLineSize() * editor->getZoom() / 100);
  case SB_LINEDOWN:
    return (INT)(current +
      editor->getLineSize() * editor->getZoom() / 100);
  case SB_THUMBPOSITION:
  case SB_THUMBTRACK:
    return si.nTrackPos;
  default:
    return current;
  }
}

VOID    EditSurface::setScrollX(INT value)
{
  if (!editor) return;

  value = max(0, value);
  value = min(scroll.max.x, value);

  // if scroll does not change, abort
  if (value == scroll.pos.x) return;

  // set grid scroll
  scroll.pos.x = value;
  editor->setView(
    (scroll.pos + Rect::fromClient(*this).getSize()) *
    100 / editor->getZoom());

  // set horizontal scroll
  SCROLLINFO si;
  si.cbSize = sizeof(si);
  si.fMask  = SIF_POS;
  si.nPos   = scroll.pos.x;
  SetScrollInfo(*this, SB_HORZ, &si, TRUE);
}

VOID    EditSurface::setScrollY(INT value)
{
  if (!editor) return;

  value = max(0, value);
  value = min(scroll.max.y, value);

  // if scroll does not change, abort
  if (value == scroll.pos.y) return;

  // set grid scroll
  scroll.pos.y = value;
  editor->setView(
    (scroll.pos + Rect::fromClient(*this).getSize()) *
    100 / editor->getZoom());

  // set vertical scroll
  SCROLLINFO si;
  si.cbSize = sizeof(si);
  si.fMask  = SIF_POS;
  si.nPos   = scroll.pos.y;
  SetScrollInfo(*this, SB_VERT, &si, TRUE);
}

VOID EditSurface::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  *iEvent.handled = TRUE;
}

VOID    EditSurface::onPaint()
{
  PAINTSTRUCT paint;
  Gdip::Graphics g(beginPaint(&paint));

  if (editor)
  {
    Geom::Rect frameRect = editor->getRect();

    pointToClient(*(LPPOINT)&frameRect.left);
    pointToClient(*(LPPOINT)&frameRect.right);

    Gdip::Rect grid = Gdip::Rect(
      frameRect.left,
      frameRect.top,
      frameRect.getWidth(),
      frameRect.getHeight());

    grid.Inflate(4, 4);
    g.ExcludeClip(grid);

    g.Clear(0xFFFFFFFF);

    g.SetClip(grid, Gdip::CombineModeUnion);
    grid.Width--;
    grid.Height--;
    grid.Inflate(-4, -4);
    g.ExcludeClip(grid);

    for (int i = 4; i >= 0; --i)
    {
      g.DrawRectangle(&Gdip::Pen(0xFFFFFFFF - ((i + 1) * 0x00222222)), grid);
      grid.Inflate(1, 1);
    }
  }
  else
  {
    g.Clear(0xFFFFFFFF);
  }

  endPaint(&paint);
}

VOID    EditSurface::onMouseDown(Event::MouseEvent& iEvent)
{
  if (editor) editor->focus();
}

VOID    EditSurface::updateScroll()
{
  // surface bounds
  Geom::Rect rect = getClient();
  // grid frame bounds
  Bounds grid = { 0, 0, 0, 0 };

  // get grid frame bounds
  if (editor)
  {
    grid = Bounds::get(5, 5,
      (LONG)(editor->getBufferWidth() * editor->getZoom() / 100),
      (LONG)(editor->getBufferHeight() * editor->getZoom() / 100));
  }

  // maximum scroll values
  scroll.max.x = max(grid.cx - rect.getWidth() + grid.x, 0);
  scroll.max.y = max(grid.cy - rect.getHeight() + grid.y, 0);

  // set scroll values
  scroll.pos.x = min(scroll.max.x, scroll.pos.x);
  scroll.pos.y = min(scroll.max.y, scroll.pos.y);

  // initialize scroll info
  SCROLLINFO info;
  info.cbSize = sizeof(info);
  info.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
  info.nMin = 0;

  // scroll surface horizontally
  info.nMax  = grid.cx;
  info.nPage = rect.getWidth() - grid.x;
  info.nPos = scroll.pos.x;
  SetScrollInfo(*this, SB_HORZ, &info, TRUE);

  // scroll surface vertically
  info.nMax  = grid.cy;
  info.nPage = rect.getHeight() - grid.y;
  info.nPos = scroll.pos.y;
  SetScrollInfo(*this, SB_VERT, &info, TRUE);

  if (editor)
  {
    // set grid bounds
    if (grid != editor->getBounds())
      editor->setBounds(grid);

    // set grid scroll
    editor->setView((scroll.pos + rect.getSize()) * 100 / editor->getZoom());
  }
}

VOID    EditSurface::scrollToPoint(const Geom::Point& pt)
{
  // surface bounds
  Geom::Rect rect = getClient();
  // surface mouse pos
  Geom::Point pos = pt;

  pos = pos.toScreen(*editor);
  pos = pos.toClient(*this);

  // scroll to mouse vertical pos
  if (pos.y < 0)
    setScrollY(getScroll().y + pos.y);
  else if (pos.y > rect.getHeight())
    setScrollY(getScroll().y + (pos.y - rect.getHeight()));

  // scroll to mouse horizontal pos
  if (pos.x < 0)
    setScrollX(getScroll().x + pos.x);
  else if (pos.x > rect.getWidth())
    setScrollX(getScroll().x + (pos.x - rect.getWidth()));
}
